use std::collections::HashMap;

use common_base::{
    futures::TryStreamExt,
    prelude::*,
    sqlx::{self, mysql::MySqlArguments, Arguments, MySql},
};
use common_core::{
    data::{
        DatasetOwner, DatasetType, DatasetUtil, DctMetadata, Metadata, MetadataType, RowsetOwner,
    },
    ServiceContextBase,
};

use super::{metadata_util::MetadataUtil, MetadataManager};

pub struct DctMetadataManager;

impl DctMetadataManager {
    pub async fn get_metadata_model(
        &self,
        srv_ctx: &mut ServiceContextBase,
        dct_id: &str,
    ) -> Result<Option<DctMetadata>> {
        let mut dct_metadata = self.load_sys_dicts(srv_ctx, dct_id).await?;

        if let Some(dct_metadata) = dct_metadata.as_mut() {
            MetadataManager::process_reference_metadata(
                srv_ctx,
                dct_id,
                Metadata::Dct(dct_metadata.clone()),
            );
            // 装入数据字典的对象定义
            self.load_do_metadata(srv_ctx, dct_metadata).await?;
            // 装入数据字典的外键引用数据字典
            self.load_fkey_dct(srv_ctx, dct_metadata)?;
            // self.load_ref_dct(srv_ctx, dct_metadata)?;
            self.load_dct_rlgl(srv_ctx, dct_metadata).await?;
            self.load_rlgl_dct_and_do(srv_ctx, dct_metadata)?;
            self.load_acjgsz(srv_ctx, dct_metadata).await?;
            self.load_custom_dct(srv_ctx, dct_metadata)?;
            self.load_sys_dct_cst(srv_ctx, dct_metadata).await?;
        }
        Ok(dct_metadata)
    }

    async fn load_sys_dicts(
        &self,
        srv_ctx: &mut ServiceContextBase,
        object_id: &str,
    ) -> Result<Option<DctMetadata>> {
        let mut arguments = MySqlArguments::default();
        arguments.add(object_id);

        let (sql, arguments) = MetadataUtil::generate_sql_and_args(
            srv_ctx,
            DctMetadata::SYS_DICTS,
            "*",
            "OBJ_ID=?",
            "",
            arguments,
            object_id,
        );

        let mut rows = sqlx::query_with::<MySql, MySqlArguments>(sql.as_str(), arguments).fetch(
            srv_ctx
                .get_connection_mut()
                .expect("can not find db_conn in context"),
        );

        while let Some(row) = rows.try_next().await? {
            return Ok(Some(DctMetadata::new(object_id, row.try_into()?)));
        }
        Ok(None)
    }

    async fn load_do_metadata(
        &self,
        srv_ctx: &mut ServiceContextBase,
        dct_metadata: &mut DctMetadata,
    ) -> Result<()> {
        // 装入此数据字典的数据对象元数据
        let do_id = dct_metadata.get_object_id().to_owned();
        if let Some(mut metadata) = MetadataManager::get_instance(MetadataType::Do)
            .get_metadata(srv_ctx, do_id.as_str())
            .await?
        {
            metadata
                .as_do_metadata_mut()
                .unwrap()
                .set_dct_metadata(dct_metadata.get_object_id());
            dct_metadata.set_do_metadata(metadata);
        }

        Ok(())
    }

    fn load_fkey_dct(
        &self,
        _srv_ctx: &mut ServiceContextBase,
        dct_metadata: &mut DctMetadata,
    ) -> Result<()> {
        if let Some(do_metadata) = dct_metadata.get_do_metadta() {
            let fkey_dct_metadata_ids = do_metadata.as_do_metadata().unwrap().get_fkey_metadatas();
            dct_metadata.set_fkey_dct_metadatas(fkey_dct_metadata_ids);
            Ok(())
        } else {
            Err(ErrorCode::IllegalStateError(format!(
                "can not find DoMetadata for DctMetadata[object_id: {}]",
                dct_metadata.get_object_id(),
            )))
        }
    }

    async fn load_dct_rlgl(
        &self,
        srv_ctx: &mut ServiceContextBase,
        dct_metadata: &mut DctMetadata,
    ) -> Result<()> {
        let mut arguments = MySqlArguments::default();
        arguments.add(dct_metadata.get_object_id());

        let (sql, arguments) = DatasetUtil::generate_sql_and_args(
            srv_ctx,
            "SYS_RLGL",
            "*",
            "DCT_ID2=?",
            "",
            arguments,
        );

        let mut rows = sqlx::query_with::<MySql, MySqlArguments>(sql.as_str(), arguments).fetch(
            srv_ctx
                .get_connection_mut()
                .expect("can not find db_conn in context"),
        );

        let mut dataset = DatasetOwner::get_instance("SYS_RLGL", DatasetType::Container);
        dataset.set_prime_key(vec!["RLGL_ID"]);

        while let Some(row) = rows.try_next().await? {
            let rowset: RowsetOwner = row.try_into()?;
            let rowset_ref = dataset.insert_rowset(rowset);
            dataset.build_prime_key_index(&rowset_ref);
        }

        dct_metadata.insert_dataset("SYS_RLGL", dataset);

        Ok(())
    }

    fn load_rlgl_dct_and_do(
        &self,
        _srv_ctx: &mut ServiceContextBase,
        dct_metadata: &mut DctMetadata,
    ) -> Result<()> {
        let mut rlgl_dct_map = HashMap::default();
        let mut rlgl_do_map = HashMap::default();
        if let Some(dataset) = dct_metadata.get_dataset("SYS_RLGL") {
            for rowset in dataset.get_rowset_list() {
                let rlgl_id = rowset.get_string("RLGL_ID");
                let dct_id_1 = rowset.get_string("DCT_ID1");
                if !dct_id_1.is_empty() {
                    rlgl_dct_map.insert(rlgl_id.to_owned(), dct_id_1.to_owned());
                }

                let do_id = rowset.get_string("RLGL_OBJID");
                if !do_id.is_empty() {
                    rlgl_do_map.insert(rlgl_id.to_owned(), do_id.to_owned());
                }
            }
        }
        dct_metadata.set_rlgl_dct_map(rlgl_dct_map);
        dct_metadata.set_rlgl_do_map(rlgl_do_map);
        Ok(())
    }

    async fn load_acjgsz(
        &self,
        srv_ctx: &mut ServiceContextBase,
        dct_metadata: &mut DctMetadata,
    ) -> Result<()> {
        let mut arguments = MySqlArguments::default();
        arguments.add(dct_metadata.get_object_id());

        let (sql, arguments) =
            DatasetUtil::generate_sql_and_args(srv_ctx, "ACJGSZ", "*", "ZD_DCT=?", "", arguments);

        let mut rows = sqlx::query_with::<MySql, MySqlArguments>(sql.as_str(), arguments).fetch(
            srv_ctx
                .get_connection_mut()
                .expect("can not find db_conn in context"),
        );

        let mut dataset = DatasetOwner::get_instance("ACJGSZ", DatasetType::Container);
        dataset.set_prime_key(vec!["UNIT_ID", "JG_DCT", "ZD_DCT"]);

        while let Some(row) = rows.try_next().await? {
            let rowset: RowsetOwner = row.try_into()?;
            let rowset_ref = dataset.insert_rowset(rowset);
            dataset.build_prime_key_index(&rowset_ref);
        }

        dct_metadata.set_acjgsz(dataset);

        Ok(())
    }

    fn load_custom_dct(
        &self,
        srv_ctx: &mut ServiceContextBase,
        dct_metadata: &mut DctMetadata,
    ) -> Result<()> {
        // Nothing to do
        Ok(())
    }

    async fn load_sys_dct_cst(
        &self,
        srv_ctx: &mut ServiceContextBase,
        dct_metadata: &mut DctMetadata,
    ) -> Result<()> {
        let mut arguments = MySqlArguments::default();
        arguments.add(dct_metadata.get_object_id());

        let (sql, arguments) = DatasetUtil::generate_sql_and_args(
            srv_ctx,
            "SYS_DCT_CST",
            "*",
            "DCT_ID=?",
            "",
            arguments,
        );

        let mut rows = sqlx::query_with::<MySql, MySqlArguments>(sql.as_str(), arguments).fetch(
            srv_ctx
                .get_connection_mut()
                .expect("can not find db_conn in context"),
        );

        let mut dataset = DatasetOwner::get_instance("SYS_DCT_CST", DatasetType::Container);
        dataset.set_prime_key(vec!["DCT_KEY", "UNIT_ID"]);

        while let Some(row) = rows.try_next().await? {
            let rowset: RowsetOwner = row.try_into()?;
            let rowset_ref = dataset.insert_rowset(rowset);
            dataset.build_prime_key_index(&rowset_ref);
        }

        Metadata::process_extend_property(
            dct_metadata.metadata_base_mut(),
            &dataset,
            "UNIT_ID",
            "DCT_KEY",
            "DCT_VALUE",
        );
        dct_metadata.set_sys_dct_cst(dataset);

        Ok(())
    }
}

// Deprecated
// impl DctMetadataManager {
//     fn load_ref_dct(
//         &self,
//         _srv_ctx: &mut ServiceContextBase,
//         dct_metadata: &mut DctMetadata,
//     ) -> Result<()> {
//         if let Some(do_metadata) = dct_metadata.get_do_metadta() {
//             if let Some(ref_do_id) = do_metadata.as_do_metadata().unwrap().get_ref_do_id() {
//                 dct_metadata.set_ref_dct_id(ref_do_id.to_owned());
//             }
//             Ok(())
//         } else {
//             Err(ErrorCode::IllegalStateError(format!(
//                 "can not find DoMetadata for DctMetadata[object_id: {}]",
//                 dct_metadata.get_object_id(),
//             )))
//         }
//     }
// }
